using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Configuration;
using System.Xml.Serialization;
using HID;
using LeisureCenter.Api;
using LeisureCenter.Api.Tools;

namespace Aspire
{
    internal enum AspireRemoteCode
    {
        Power = 0x00,
        Setup, Mute,
        PC, Music, Video, Picture, TV, FM,
        Previous, Next, PlayPause, Rewind, Forward, Stop, Eject,
        Repeat, Random,
        Menu, Slide_Language, Subtitle_Rotate, Audio,
        Back, Mem,
        Up, Left, Ok, Right, Down,
        MemoUp, VolumeUp, ChannelUp, MemoDown = 0x40, VolumeDown, ChannelDown,
        Digit1, Digit2, Digit3, Digit4, Digit5, Digit6, Digit7, Digit8, Digit9,
        Asterisk, Digit0, Sharp
    }

    internal class CodeCommandsDictionary : SerializableDictionary<AspireRemoteCode, LeisureCenterCommand>
    {
        protected override string NS
        {
            get
            {
                return base.NS + "/AspirePlugin";
            }
        } 
    }

    public class AspireRemoteControl : Control
    {
        private AspirePlugin plugin;
        private CodeCommandsDictionary commands;
        DeviceManagement _MyDeviceManagement = new DeviceManagement();
        Thread thread;
        bool terminated;
        Hid _MyHID = new Hid();
        Hid.InputReport inputReport;
        int _HIDHandle;
        string _MyDevicePathName;
        bool _MyDeviceDetected;
        int _ReadHandle;

        // Define a class of delegates that point to the Hid.DeviceReport.Read function.
        // The delegate has the same parameters as Hid.DeviceReport.Read.
        // Used for asynchronous reads from the device.
        private delegate void ReadInputReportDelegate(int readHandle, int hidHandle, ref bool myDeviceDetected, ref byte[] readBuffer, ref bool success);

        private void FindRemote()
        {
            bool DeviceFound = false;
            string[] DevicePathName = new string[128];
            string GUIDString;
            System.Guid HidGuid;
            int MemberIndex = 0;
            ushort MyProductID = 0xE00D;
            ushort MyVendorID = 0x147A;
            int Result = 0;
            FileIOApiDeclarations.SECURITY_ATTRIBUTES Security = new HID.FileIOApiDeclarations.SECURITY_ATTRIBUTES();
            //bool Success = false;

            HidGuid = Guid.Empty;
            _MyDeviceDetected = false;

            // Values for the SECURITY_ATTRIBUTES structure:
            Security.lpSecurityDescriptor = 0;
            Security.bInheritHandle = System.Convert.ToInt32(true);
            Security.nLength = Marshal.SizeOf(Security);

            /*
              API function: 'HidD_GetHidGuid
              Purpose: Retrieves the interface class GUID for the HID class.
              Accepts: 'A System.Guid object for storing the GUID.
              */

            HidApiDeclarations.HidD_GetHidGuid(ref HidGuid);
            //Debug.WriteLine(_MyDebugging.ResultOfAPICall("GetHidGuid"));

            // Display the GUID.
            GUIDString = HidGuid.ToString();
            //Debug.WriteLine("  GUID for system HIDs: " + GUIDString);

            // Fill an array with the device path names of all attached HIDs.
            DeviceFound = _MyDeviceManagement.FindDeviceFromGuid(HidGuid, ref DevicePathName);

            // If there is at least one HID, attempt to read the Vendor ID and Product ID
            // of each device until there is a match or all devices have been examined.

            if (DeviceFound)
            {
                MemberIndex = 0;
                do
                {
                    // ***
                    // API function:
                    // CreateFile
                    // Purpose:
                    // Retrieves a handle to a device.
                    // Accepts:
                    // A device path name returned by SetupDiGetDeviceInterfaceDetail
                    // The type of access requested (read/write).
                    // FILE_SHARE attributes to allow other processes to access the device while this handle is open.
                    // A Security structure. Using Null for this may cause problems under Windows XP.
                    // A creation disposition value. Use OPEN_EXISTING for devices.
                    // Flags and attributes for files. Not used for devices.
                    // Handle to a template file. Not used.
                    // Returns: a handle that enables reading and writing to the device.
                    // ***

                    _HIDHandle = FileIOApiDeclarations.CreateFile
                        (DevicePathName[MemberIndex],
                        FileIOApiDeclarations.GENERIC_READ | FileIOApiDeclarations.GENERIC_WRITE,
                        FileIOApiDeclarations.FILE_SHARE_READ | FileIOApiDeclarations.FILE_SHARE_WRITE,
                        ref Security,
                        FileIOApiDeclarations.OPEN_EXISTING, 0, 0);

                    if (_HIDHandle != FileIOApiDeclarations.INVALID_HANDLE_VALUE)
                    {
                        // The returned handle is valid,
                        // so find out if this is the device we're looking for.

                        // Set the Size property of DeviceAttributes to the number of bytes in the structure.
                        //_MyHID.DeviceAttributes.Size = _MyHID.DeviceAttributes.ToString().Length;
                        _MyHID.DeviceAttributes.Size = Marshal.SizeOf(_MyHID.DeviceAttributes);

                        // ***
                        // API function:
                        // HidD_GetAttributes
                        // Purpose:
                        // Retrieves a HIDD_ATTRIBUTES structure containing the Vendor ID,
                        // Product ID, and Product Version Number for a device.
                        // Accepts:
                        // A handle returned by CreateFile.
                        // A pointer to receive a HIDD_ATTRIBUTES structure.
                        // Returns:
                        // True on success, False on failure.
                        // ***

                        Result = HidApiDeclarations.HidD_GetAttributes(_HIDHandle, ref _MyHID.DeviceAttributes);

                        if (Result != 0)
                        {
                            // Find out if the device matches the one we're looking for.
                            if ((_MyHID.DeviceAttributes.VendorID == MyVendorID) & (_MyHID.DeviceAttributes.ProductID == MyProductID))
                            {
                                _MyDeviceDetected = true;

                                // Save the DevicePathName so OnDeviceChange() knows which name is my device.
                                _MyDevicePathName = DevicePathName[MemberIndex];
                            }
                            else
                            {
                                // It's not a match, so close the handle.
                                _MyDeviceDetected = false;

                                Result = FileIOApiDeclarations.CloseHandle(_HIDHandle);
                            }
                        }
                        else
                        {
                            // There was a problem in retrieving the information.
                            _MyDeviceDetected = false;
                            Result = FileIOApiDeclarations.CloseHandle(_HIDHandle);
                        }
                    }

                    // Keep looking until we find the device or there are no more left to examine.
                    MemberIndex = MemberIndex + 1;

                } while (!((_MyDeviceDetected == true) | (MemberIndex == DevicePathName.Length)));
            }

            if (_MyDeviceDetected)
            {
                // Learn the capabilities of the device.
                _MyHID.Capabilities = _MyHID.GetDeviceCapabilities(_HIDHandle);

                // Get and display the Input report buffer size.
                //_MyHID.GetNumberOfInputBuffers(_HIDHandle, ref NumberOfInputBuffers);

                // Get another handle to use in overlapped ReadFiles (for requesting Input reports).
                _ReadHandle = FileIOApiDeclarations.CreateFile(_MyDevicePathName, FileIOApiDeclarations.GENERIC_READ | FileIOApiDeclarations.GENERIC_WRITE, FileIOApiDeclarations.FILE_SHARE_READ | FileIOApiDeclarations.FILE_SHARE_WRITE, ref Security, FileIOApiDeclarations.OPEN_EXISTING, FileIOApiDeclarations.FILE_FLAG_OVERLAPPED, 0);

                // (optional)
                // Flush any waiting reports in the input buffer.
                _MyHID.FlushQueue(_ReadHandle);
            }
        }

        private void FindRemoteAgain()
        {
            FileIOApiDeclarations.SECURITY_ATTRIBUTES Security = new HID.FileIOApiDeclarations.SECURITY_ATTRIBUTES();
            //bool Success = false;

            _MyDeviceDetected = false;

            // Values for the SECURITY_ATTRIBUTES structure:
            Security.lpSecurityDescriptor = 0;
            Security.bInheritHandle = System.Convert.ToInt32(true);
            Security.nLength = Marshal.SizeOf(Security);

            _HIDHandle = FileIOApiDeclarations.CreateFile
                (_MyDevicePathName,
                FileIOApiDeclarations.GENERIC_READ | FileIOApiDeclarations.GENERIC_WRITE,
                FileIOApiDeclarations.FILE_SHARE_READ | FileIOApiDeclarations.FILE_SHARE_WRITE,
                ref Security,
                FileIOApiDeclarations.OPEN_EXISTING, 0, 0);

            if (_HIDHandle != FileIOApiDeclarations.INVALID_HANDLE_VALUE)
            {
                // The returned handle is valid,
                // so find out if this is the device we're looking for.

                // Set the Size property of DeviceAttributes to the number of bytes in the structure.
                //_MyHID.DeviceAttributes.Size = _MyHID.DeviceAttributes.ToString().Length;
                _MyHID.DeviceAttributes.Size = Marshal.SizeOf(_MyHID.DeviceAttributes);

                HidApiDeclarations.HidD_GetAttributes(_HIDHandle, ref _MyHID.DeviceAttributes);
                _MyDeviceDetected = true;
            }

            if (_MyDeviceDetected)
            {
                // Learn the capabilities of the device.
                _MyHID.Capabilities = _MyHID.GetDeviceCapabilities(_HIDHandle);

                // Get and display the Input report buffer size.
                //_MyHID.GetNumberOfInputBuffers(_HIDHandle, ref NumberOfInputBuffers);

                // Get another handle to use in overlapped ReadFiles (for requesting Input reports).
                _ReadHandle = FileIOApiDeclarations.CreateFile(_MyDevicePathName, FileIOApiDeclarations.GENERIC_READ | FileIOApiDeclarations.GENERIC_WRITE, FileIOApiDeclarations.FILE_SHARE_READ | FileIOApiDeclarations.FILE_SHARE_WRITE, ref Security, FileIOApiDeclarations.OPEN_EXISTING, FileIOApiDeclarations.FILE_FLAG_OVERLAPPED, 0);

                // (optional)
                // Flush any waiting reports in the input buffer.
                _MyHID.FlushQueue(_ReadHandle);
            }
        }

        private void ReadData()
        {
			// Set the size of the Input report buffer.
			// Subtract 1 from the value in the Capabilities structure because
			// the array begins at index 0.
			byte[] InputReportBuffer = new byte[_MyHID.Capabilities.InputReportByteLength-1 + 1];
            bool Success = true;
            inputReport = new Hid.InputReport();

            while (!terminated)
            {
                if (!_MyDeviceDetected)
                {
                    //MessageBox.Show("remote lost");
                    FindRemoteAgain();
                    if (!_MyDeviceDetected)
                    {
                        return;
                        //MessageBox.Show("remote not found !");
                    }
                }


                inputReport.Read(_ReadHandle, _HIDHandle, ref _MyDeviceDetected, ref InputReportBuffer, ref Success);

                if (Success && InputReportBuffer != null && InputReportBuffer.Length == 5)
                {
                    //if ((InputReportBuffer[0] != 0) || (InputReportBuffer[1] != 0) || (InputReportBuffer[2] != 0) || (InputReportBuffer[3] != 0) || (InputReportBuffer[4] != 0))
                      //  MessageBox.Show(String.Format("{0} {1} {2} {3} {4}", InputReportBuffer[0], InputReportBuffer[1], InputReportBuffer[2], InputReportBuffer[3], InputReportBuffer[4]));
                    if (InputReportBuffer[0] == 0 && InputReportBuffer[2] == 224 && InputReportBuffer[3] == 13 && (InputReportBuffer[4] == 0 || InputReportBuffer[4] == 1))
                    {
                        //MessageBox.Show(InputReportBuffer[1].ToString());
                        HandleInput((AspireRemoteCode)InputReportBuffer[1]);
                    }
                }
            }
        }

        private void HandleInput(AspireRemoteCode code)
        {
            switch (code)
            {
                case AspireRemoteCode.Music:
                case AspireRemoteCode.Video:
                case AspireRemoteCode.Picture:
                case AspireRemoteCode.TV:
                case AspireRemoteCode.FM:
                    plugin.RestorePcMode();
                    break;
            }
            LeisureCenterCommand command;
            if (commands.TryGetValue(code, out command))
            {
                plugin.leisureCenterInstance.CommandsManager.EnqueueCommand(command);
            }
        }

        public AspireRemoteControl(AspirePlugin plugin)
        {
            this.plugin = plugin;
            FindRemote();
            if (_MyDeviceDetected)
            {
                thread = new Thread(ReadData);
                thread.Start();
            }
            //AspireSettings settings = new AspireSettings();
            commands = new CodeCommandsDictionary();
            commands.Add(AspireRemoteCode.Up, LeisureCenterCommand.Up);
            commands.Add(AspireRemoteCode.Down, LeisureCenterCommand.Down);
            commands.Add(AspireRemoteCode.Left, LeisureCenterCommand.Left);
            commands.Add(AspireRemoteCode.Right, LeisureCenterCommand.Right);
            commands.Add(AspireRemoteCode.Picture, LeisureCenterCommand.PageDown);
            commands.Add(AspireRemoteCode.Video, LeisureCenterCommand.PageUp);
            commands.Add(AspireRemoteCode.Ok, LeisureCenterCommand.Select);
            commands.Add(AspireRemoteCode.Next, LeisureCenterCommand.Next);
            commands.Add(AspireRemoteCode.PlayPause, LeisureCenterCommand.PlayPause);
            commands.Add(AspireRemoteCode.Stop, LeisureCenterCommand.Stop);
            commands.Add(AspireRemoteCode.Previous, LeisureCenterCommand.Previous);
            commands.Add(AspireRemoteCode.Menu, LeisureCenterCommand.ToggleFullScreen);
            commands.Add(AspireRemoteCode.Music, LeisureCenterCommand.Back);
            commands.Add(AspireRemoteCode.PC, LeisureCenterCommand.ShowHide);
            commands.Add(AspireRemoteCode.TV, LeisureCenterCommand.FastRewind);
            commands.Add(AspireRemoteCode.FM, LeisureCenterCommand.FastForward);
            commands.Add(AspireRemoteCode.Digit1, LeisureCenterCommand.AlternateSelect);
            commands.Add(AspireRemoteCode.Digit0, LeisureCenterCommand.OptionsSelect);
            commands.Add(AspireRemoteCode.Digit2, LeisureCenterCommand.ShowOsd);
            commands.Add(AspireRemoteCode.Digit3, LeisureCenterCommand.Refresh);
            //commands = settings.Commands;
            //settings.Commands = commands;
            //AspireSettings.Default.Commands = commands;
            //AspireSettings.Default.Save();
            //settings.Save();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                terminated = true;
                if (thread != null && thread.ThreadState != ThreadState.Unstarted)
                {
                    if (!thread.Join(1000))
                        thread.Abort();
                    thread = null;
                }
            }
            base.Dispose(disposing);
        }

        public bool RemoteFound { get { return _MyDeviceDetected; } }
    }
}
